use super::{
    err, err_value, json, ok, throw_err, try_rm_task, Base, Client, ECode, Lock, MapSv, Resp,
    TBase, VBase, VMap, Value,
};
use std::collections::{HashMap, HashSet};
use tracing::{instrument, warn};

pub struct Pair;

impl Base for Pair {}
impl TBase for Pair {}

impl Pair {
    pub async fn do_cmd(&self, para: &Value, cmd: &str, client: &Client, lock: Lock) -> Resp {
        let data = para.ckobj()?;
        let line = data.ck_str("line")?;

        match cmd {
            "group" => self.do_group(line, data, client).await,
            "task" => self.do_task(line, data, client, lock).await,
            "bsn" => self.do_bsn(line, data, client, lock).await,
            _ => throw_err!(ECode::NoServe, "no pair serve"),
        }
    }

    #[instrument(skip_all, name = "do_group")]
    async fn do_group(&self, line: &str, data: &MapSv, client: &Client) -> Resp {
        let tp = data.ck_str("k")?;

        let gtp = match tp {
            "pair" => "GP",
            "any" => "GA",
            _ => throw_err!(ECode::Para, "pair type error"),
        };

        warn!("task step: get task one week");

        let cmd = json!({
            "line": line,
            "s": self.pre_cur_time_day(7, "%Y-%m-%d %H:%M:%S")?,
            "e": base::cur_time("%Y-%m-%d %H:%M:%S"),
        });

        let task_body = client.cfgdb(&cmd, "task/list").await?;
        let task_body = task_body.ck_array("data")?;

        let mut ret = Vec::new();

        for d in task_body.iter() {
            let d = d.ckobj()?;

            let sn = d.ck_str("sn")?;
            let tech = d.ck_str("tech")?;

            if !sn.starts_with(gtp) {
                continue;
            }

            ret.push(json!({
                "sn": sn,
                "tech": tech,
            }));
        }

        ok!(ret)
    }

    #[instrument(skip_all, name = "do_bsn")]
    async fn do_bsn(&self, line: &str, data: &MapSv, client: &Client, lock: Lock) -> Resp {
        let _ = lock.lock().await;

        let bsn = data.ck_str("bsn")?;
        let task = data.ck_str("task")?;
        let mt = data.ck_str("mt")?;
        let dw = data.ck_f64("dw")?;
        let std = data.ck_f64("std")?;
        let sub = data.ck_f64("sub")?;
        let ball = data.ck_f64("ball")?;

        let cmd = json!({
            "line": line,
            "k": task,
        });

        let body = client.cfgdb(&cmd, "psn/list").await?;
        let body = body.ck_array("data")?;

        let mut psn_val = Vec::new();

        for psn in body.iter() {
            let psn = psn.ck_str("sn")?;

            psn_val.push(json!({
                "sn": psn,
                "bsn": bsn,
                "std": std,
                "sub": sub,
                "dw": dw,
            }));
        }

        let cmd = json!({
            "line": line,
            "v": psn_val,
        });

        let _ = client.cfgdb(&cmd, "psn/mmd").await?;

        let cmd = json!({
            "line": line,
            "k": task,
            "v": {
                "bsn": bsn,
                "ballch": {
                    "sn": bsn,
                    "std": std,
                    "sub": sub,
                    "dw": dw,
                    "ball": ball,
                    "mt": mt,
                }
            },
        });

        let _ = client.cfgdb(&cmd, "task/md").await?;

        ok!()
    }

    #[instrument(skip_all, name = "do_task")]
    async fn do_task(&self, line: &str, data: &MapSv, client: &Client, lock: Lock) -> Resp {
        let _ = lock.lock().await;

        let tp = data.ck_str("k")?;
        let tech = data.ck_str("tech")?;
        let val = data.ck_array("v")?;

        let gtp = match tp {
            "pair" => "GP",
            "any" => "GA",
            _ => throw_err!(ECode::Para, "pair type error"),
        };

        warn!("task step: gen task sn");

        let cmd = json!({
            "line": line,
            "rule": "task",
        });

        let body = client.cfgdb(&cmd, "code/gen").await?;

        let task_sn = body.ck_obj("data")?.ck_str("psn")?;
        let task_sn = format!("{gtp}{task_sn}");

        warn!("task step: create task");

        let cmd = json!({
            "line": line,
            "v": {
                "sn": task_sn,
                "tech": tech,
                "pstart": base::cur_time("%Y-%m-%d %H:%M:%S"),
                "pend": self.next_cur_time_day(1, "%Y-%m-%d %H:%M:%S")?,
                "state": "open",
                "pnum": val.len(),
            },
        });

        let _ = client.cfgdb(&cmd, "task/add").await?;

        warn!("task step: add psn");

        let cmd = json!({
            "line": line,
            "k": task_sn,
            "v": val,
        });
        let _ = client.cfgdb(&cmd, "task/movepsn").await?;

        let mut psn_val = Vec::new();
        let mut task_val = HashMap::<String, HashSet<String>>::new();

        for psn in val.iter() {
            let psn = psn.ckstr()?;

            let cmd = json!({
                "line": line,
                "k": psn,
            });
            if let Ok(psn_body) = client.cfgdb(&cmd, "psn/get").await {
                let task = psn_body.ck_obj("data")?.ck_str("task")?;

                match task_val.get_mut(task) {
                    Some(v) => {
                        v.insert(psn.to_string());
                    }
                    None => {
                        let mut tmp = HashSet::new();
                        tmp.insert(psn.to_string());

                        task_val.insert(task.to_string(), tmp);
                    }
                }
            }

            psn_val.push(json!({
                "sn": psn,
                "task": task_sn,
                "tech": tech,
            }));
        }

        warn!("task step: remove psn form task");

        for (k, v) in task_val.iter() {
            let mut tmp = Vec::new();
            for d in v {
                tmp.push(d);
            }

            let cmd = json!({
                "line": line,
                "k": k,
                "v": tmp,
                "rmpsn": false,
            });

            let _ = client.cfgdb(&cmd, "task/rmpsn").await?;

            let _ = try_rm_task(line, k, client).await;
        }

        warn!("task step: update psn");

        let cmd = json!({
            "line": line,
            "v": psn_val,
        });

        let _ = client.cfgdb(&cmd, "psn/mmd").await?;

        ok!()
    }
}
